/*
 * File:   Factory.cpp
 * Author: Elevator
 *
 * Created on 9 Июнь 2013 г., 14:59
 */

#include "Factory.h"
#include <math.h>
#include <vector>

Factory Factory::_factory;

const Factory& Factory::GetInstance( )
{
	return _factory;
}

typedef Pair<Vec2> Vec2Pair;

Vec2Pair CalculateConvexTangent( const Circle& circleA, const Circle& circleB );

Map* Factory::CreateMap( ) const
{
	return new Map( );
}

void Factory::DestroyMap( Map* map ) const
{
	delete map;
}

Obstacle* Factory::CreateObstacle( std::string id ) const
{
	Obstacle* obstacle = new Obstacle( id );
	return obstacle;
}

void Factory::DestroyObstacle( Obstacle* obstacle ) const
{
	for( IteratorPtr<physics::IBody*> bodies( obstacle->GetBodies( )->GetIterator( ) );
			!bodies->IsEnd( );
			bodies->Next( ) )
	{
		DestroyBody( bodies->Current( ) );
	}
	delete obstacle;
}

Detail* Factory::CreateDetail( std::string id, const Transform& pos, const ICollection<Circle>* circles, float mass, float inertia, float friction, float restitution ) const
{
	Detail* detail = new Detail( id );
	detail->SetBody( CreateBody( physics::IBody::Dynamic, pos, circles, mass, inertia, friction, restitution ) );
	return detail;
}

void Factory::DestroyDetail( Detail* detail ) const
{
	DestroyBody( detail->GetBody( ) );
	delete detail;
}

physics::IBody* Factory::CreateBody( physics::IBody::Type bodyType, const Transform& pos, const ICollection<Circle>* circles, float mass, float inertia, float friction, float restitution ) const
{
	physics::IEngine* engine = physics::Physics::GetEngine( );
	physics::IBody* body = engine->CreateBody( bodyType, pos, Transform::ZERO, mass, inertia );

	//Заполняем вспомогательный массив
	std::vector<Circle> circlesVector = circles->GetStdVector( );
	int count = circlesVector.size( );

	if( count == 0 )return body;
	if( count == 1 )
	{
		physics::ICircleShape* circleShape = engine->CreateCircleShape( circlesVector[0].Position, circlesVector[0].Radius );
		body->CreateCollider( friction, restitution, circleShape );
		return body;
	}

	Vector<Vec2> vertices;
	for( int i = 0; i < count; ++i )
	{
		Circle current = circlesVector[i];
		Circle next = circlesVector[( i + 1 ) % count];

		physics::ICircleShape* circleShape = engine->CreateCircleShape( current.Position, current.Radius );
		body->CreateCollider( friction, restitution, circleShape );
		Vec2Pair vertexPair = CalculateConvexTangent( current, next );

		vertices.Add( vertexPair.A );
		if( next.Radius != 0.0f )
			vertices.Add( vertexPair.B );
	}
	physics::IPolygonShape* polygonShape = engine->CreatePolygonShape( Transform::ZERO );
	polygonShape->SetVertices( &vertices );

	body->CreateCollider( friction, restitution, polygonShape );

	return body;
}

void Factory::DestroyBody( physics::IBody* body ) const
{
	physics::IEngine* engine = physics::Physics::GetEngine( );
	engine->DestroyBody( body );
	delete body;
}

enum Direction
{
	Unknown, Left, Right
};

Vec2 GetTangentPoint( const Vec2& point, const Circle& circle, Direction direction );
Vec2Pair GetDoubleTangentPoint( const Circle& smallerCircle, const Circle& biggerCircle, Direction direction );

Vec2Pair CalculateConvexTangent( const Circle& circleA, const Circle& circleB )
{
	Vec2 a = circleA.Position;
	Vec2 b = circleB.Position;

	float rA = circleA.Radius;
	float rB = circleB.Radius;

	if( rA == 0 && rB == 0 )
		return Vec2Pair( a, b );

	if( rA == rB )
	{
		Vec2 vec = b - a;
		Vec2 perpendicular = vec.RotateRight( ).Normalize( ) * rA;
		return Vec2Pair( a + perpendicular, b + perpendicular );
	}

	if( rA == 0.0f )
	{
		Vec2 tangentPoint = GetTangentPoint( a, circleB, Direction::Right );
		return Vec2Pair( a, tangentPoint );
	}

	if( rB == 0.0f )
	{
		Vec2 tangentPoint = GetTangentPoint( b, circleA, Direction::Left );
		return Vec2Pair( tangentPoint, b );
	}

	if( rA < rB )
		return GetDoubleTangentPoint( circleA, circleB, Direction::Right );

	if( rB < rA )
		return GetDoubleTangentPoint( circleB, circleA, Direction::Left );

	//сюда не должно зайти
	return Vec2Pair( a, b );
}

Vec2Pair GetDoubleTangentPoint( const Circle& smallerCircle, const Circle& biggerCircle, Direction direction )
{
	Vec2 c1 = smallerCircle.Position;
	Vec2 c2 = biggerCircle.Position;

	float r1 = smallerCircle.Radius;
	float r2 = biggerCircle.Radius;

	//пересечение касательной с центральной линией
	Vec2 intersection = ( c1 - c2 ) / ( r2 - r1 ) * r2;

	Vec2 tangentPoint2 = GetTangentPoint( intersection, biggerCircle, direction );
	Vec2 radiusNormal = ( tangentPoint2 - c2 ).Normalize( );
	Vec2 tangentPoint1 = c1 + radiusNormal * r1;

	switch( direction )
	{
	case Left:
		return Vec2Pair( tangentPoint2, tangentPoint1 );
	case Right:
		return Vec2Pair( tangentPoint1, tangentPoint2 );
	default:
		return Vec2Pair( c1, c2 );
	}
}

Vec2 GetTangentPoint( const Vec2& point, const Circle& circle, Direction direction )
{
	float hypotenuse = Distance( point, circle.Position );
	float legB = circle.Radius;
	float legA = sqrt( hypotenuse * hypotenuse - legB * legB );

	float legA1 = legA * legA / hypotenuse;
	float legC1 = legB * legA / hypotenuse;

	Vec2 normal = ( circle.Position - point ).Normalize( );
	Vec2 middle = normal * legA1;

	switch( direction )
	{
	case Left:
		return point + middle + normal.GetLeftRotation( ) * legC1;
	case Right:
		return point + middle + normal.GetRightRotation( ) * legC1;
	default:
		return point + middle;
	}
}

Junction* Factory::CreateJunction( std::string id, const Detail* detailA, const Detail* detailB, Vec2 pos, float torque ) const
{
	Junction* junction = new Junction( id );
	physics::IBody* bodyA = detailA->GetBody( );
	physics::IBody* bodyB = detailB->GetBody( );

	physics::IEngine* engine = physics::Physics::GetEngine( );
	physics::IHingeJoint* hingeJoint = engine->CreateHingeJoint(
										   bodyA, bodyB, pos, false,
										   false, 0.0f, 0.0f,
										   true, torque, 0.0f );

	junction->SetJoint( hingeJoint );
	return junction;
}

void Factory::DestroyJunction( Junction* junction ) const
{
	physics::IEngine* engine = physics::Physics::GetEngine( );
	engine->DestroyHingeJoint( junction->GetJoint( ) );
	delete junction;
}

Creature* Factory::CreateCreature( std::string id ) const
{
	return new Creature( id );
}

void Factory::DestroyCreature( Creature* creature ) const
{
	delete creature;
}
